import abc
import os
import os.path

import jieba
import jieba.analyse
import regex
import requests
import numpy as np
import matplotlib.pyplot as plt
from geopy.distance import geodesic
from PIL import Image
from matplotlib.font_manager import FontProperties
from pyecharts import Geo, Pie, Line, Bar
from wordcloud import (
    WordCloud,
    STOPWORDS
)

from dp_analysis.config import (
    base_font_path,
    base_stopwords_path,
    base_save_path,
    base_refer_picture_path,
    bd_key
)

pattern = regex.compile(r"/")


class Base(metaclass=abc.ABCMeta):
    """
    基类
    """
    def __init__(self):
        super().__init__()
        self.stopwords_set = set()

    @abc.abstractmethod
    def analyse(self, *args, **kwargs):
        pass

    @staticmethod
    def participle(data):
        """
        分词
        :param data: 待分词单条数据
        """
        # # data 为待提取的文本
        # # topK 为返回几个 TF/IDF 权重最大的关键词，默认值为 20
        # # withWeight 为是否一并返回关键词权重值，默认值为 False
        # for d in jieba.analyse.extract_tags(data, topK=15, withWeight=False):
        #     yield d
        for d in jieba.cut(data):
            yield d

    def read_stopwords(self):
        """
        读取停用词
        """
        # 添加空白符
        for blank_character in ['\r\n', '\n', ' ', '\t']:
            self.stopwords_set.add(blank_character)

        with open(base_stopwords_path, 'r', encoding="utf-8") as fr:
            for line in fr.readlines():
                line = line.strip()
                if not line:
                    continue
                self.stopwords_set.add(line)

    @staticmethod
    def output(data):
        """
        设置控制台输出字体的颜色
        """
        # return f"\033[1;31m{data}\033[0m"
        return data


class AnalyseBase(Base):
    """
    分析
    """
    def __init__(self):
        super().__init__()

    def analyse(self, *args, **kwargs):
        pass


class RecommendBase(Base):
    """
    推荐
    """
    def __init__(self):
        super().__init__()

        # 获取百度API的url
        self.map_url = "http://api.map.baidu.com/geocoder?address=%s&output=json&key={bd_key}".format(bd_key=bd_key)

    def analyse(self, *args, **kwargs):
        pass

    @staticmethod
    def calc_distance(location_1, location_2):
        """
        获取两个位置之间的距离，（单位：KM）
        :param location_1: 位置1：（经度，纬度）
        :param location_2: 位置2：（经度，纬度）
        """
        return geodesic(location_1, location_2).km

    def calc_lat_lng(self, address):
        """
        通过地址获取经纬度
        :param address: 新店铺所在的详细地址
        :return [纬度，经度] or None
        """
        try:
            url = self.map_url % address
            response = requests.get(url)
            answer = response.json()
            return [answer['result']['location']['lat'], answer['result']['location']['lng']]
        except Exception as _:
            return None


class DrawBase(Base):
    """
    绘图分析的基类
    """
    def __init__(self):
        super().__init__()
        self.my_font = FontProperties(fname=base_font_path)

    def analyse(self, *args, **kwargs):
        pass

    def draw_word_cloud(self, region, title, text_list, filename, save_path, background_color="black",
                        max_font_size=50, random_state=30, width=800, height=600, collocations=False):
        """
        绘制词云
        :param region: 地区
        :param title: 图片标题
        :param text_list: 文本列表
        :param filename: 图片保存名称
        :param save_path: 保存路径
        :param background_color: 背景图片颜色
        :param max_font_size: 字体最大值
        :param random_state: 设置有多少种随机生成状态，即有多少种配色方案
        :param width: 指定词云对象生成图片的宽度，默认400像素
        :param height: 指定词云对象生成图片的高度，默认200像素
        :param collocations: 当传入排序后的词表时，会导致同一个词出现两次，使用 collocations 消除
        """

        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        print("开始绘制图云...")
        for picture_name in os.listdir(base_refer_picture_path):
            background_picture = np.array(Image.open(os.path.join(base_refer_picture_path, picture_name)))
            stopwords = self.stopwords_set | set(STOPWORDS)
            wc = WordCloud(
                background_color=background_color,
                mask=background_picture,
                stopwords=stopwords,
                font_path=base_font_path,
                max_font_size=max_font_size,
                random_state=random_state,
                width=width,
                height=height,
                collocations=collocations
            )
            wc.generate(" ".join(text_list))
            plt.imshow(wc, interpolation='bilinear')
            # 不显示坐标轴
            plt.axis("off")
            # 绘制标题
            plt.title(title, fontproperties=self.my_font, fontsize=14)
            # plt.show()
            picture_name, suffix = picture_name.split(".")
            wc.to_file(os.path.join(save_path, filename + f"({picture_name}).{suffix}"))
            print(f"{picture_name}图片绘制完成...")
        print(f"{region}地区处理完成！\n\n")

    def draw_word_cloud_v2(self, region, title, text_list, filename, save_path, background_color="white",
                        min_font_size=30, max_font_size=100, random_state=30, width=800,
                        height=600, collocations=False):
        """
        绘制词云
        :param region: 地区
        :param title: 图片标题
        :param text_list: 文本列表
        :param filename: 图片保存名称
        :param save_path: 保存路径
        :param background_color: 背景图片颜色
        :param min_font_size: 字体的最小值
        :param max_font_size: 字体最大值
        :param random_state: 设置有多少种随机生成状态，即有多少种配色方案
        :param width: 指定词云对象生成图片的宽度，默认400像素
        :param height: 指定词云对象生成图片的高度，默认200像素
        :param collocations: 当传入排序后的词表时，会导致同一个词出现两次，使用 collocations 消除
        """

        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        print("开始绘制图云...")
        stopwords = self.stopwords_set | set(STOPWORDS)
        wc = WordCloud(
            background_color=background_color,
            stopwords=stopwords,
            font_path=base_font_path,
            min_font_size=min_font_size,
            max_font_size=max_font_size,
            random_state=random_state,
            width=width,
            height=height,
            collocations=collocations
        )
        wc.generate(" ".join(text_list))
        plt.imshow(wc, interpolation='bilinear')
        # 不显示坐标轴
        plt.axis("off")
        # 绘制标题
        plt.title(title, fontproperties=self.my_font, fontsize=14)
        # plt.show()
        wc.to_file(os.path.join(save_path, "{}.png".format(filename)))
        print(f"{region}地区处理完成！\n\n")

    @staticmethod
    def draw_map(name, attr, values, *, map_type, visual_range, geo_cities_coords, save_path,
                 visual_pos=250, visual_top="center", is_piecewise=False, is_visualmap=True, pieces=list(),
                 visual_range_text=['low', 'hight'], symbol_size=5, visual_split_number=5, visual_text_color="#000"):
        """
        绘制热力图
        :param name: 图例名称
        :param attr: 属性名称
        :param values: 属性所对应的值，这里设置为评论数量
        :param map_type: 指定组件的允许的最小值与最大值
        :param visual_range: 指定组件允许的最小值与最大值
        :param geo_cities_coords: 用户自定义地区经纬度，类似如 {'阿城': [126.58, 45.32],} 这样的字典
        :param save_path: 文件保存路径
        :param visual_pos: visualmap 组件条距离左侧的位置，默认为’left’。有’right’, ‘center’, ‘right’可选，也可为百分数或整数。
        :param visual_top: visualmap 组件条距离顶部的位置，默认为’top’。有’top’, ‘center’, ‘bottom’可选，也可为百分数或整数。
        :param is_piecewise: 是否将组件转换为分段型（默认为连续型）
        :param visual_split_number: 分段型中分割的段数，在设置为分段型时生效
        :param is_visualmap: 是否使用视觉映射组件
        :param symbol_size: 标记图形大小。默认为 12
        :param visual_range_text: 设置两端文本
        :param visual_text_color: 两端文本颜色
        :param pieces: 自定义『分段式视觉映射组件（visualMapPiecewise）』的每一段的范围，以及每一段的文字，以及每一段的特别的样式。
        （仅在 is_piecewise 为 True 时生效）
        """
        print(f"开始生成{name}地区网页")
        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        geo = Geo(
            f"{name}地区热门餐厅分布图",
            '',
            title_color="black",
            title_pos="center",
            width=1200,
            height=900,
        )
        geo.add(
            '',
            attr,
            values,
            symbol_size=symbol_size,
            visual_range=visual_range,
            is_piecewise=is_piecewise,
            is_visualmap=is_visualmap,
            maptype=map_type,
            visual_pos=visual_pos,
            visual_top=visual_top,
            visual_range_text=visual_range_text,
            visual_split_number=visual_split_number,
            geo_cities_coords=geo_cities_coords,
            visual_text_color=visual_text_color,
            pieces=pieces,
            geo_normal_color="#696969"
        )
        geo.render(path=os.path.join(save_path, f"{name}热门餐厅地理坐标图.html"))
        print(f"{name}地区网页生成完成！\n\n")

    @staticmethod
    def draw_pie(name, attr, value, save_path, save_file):
        """
        绘制饼状图
        """
        print(f"开始绘制{name}!")

        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        pie = Pie(name, title_pos='center')
        pie.add(
            "",
            attr,
            value,
            radius=[40, 75],
            label_text_color=None,
            is_label_show=True,
            legend_orient="vertical",
            legend_pos="left",
        )
        pie.render(path=os.path.join(save_path, save_file))
        print(f"{name}绘制完成")

    @staticmethod
    def draw_top10_line(comp_list, shop_id_list, taste_list, env_list, serve_list, save_path, region, cuisine):
        """
        绘制 top10 线形图
        """
        if not shop_id_list:
            return

        cuisine = pattern.sub("+", cuisine)

        print(f"开始绘制{region}地区{cuisine}菜系线形图...")
        save_path = os.path.join(base_save_path, os.path.join(save_path, region))
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        line = Line(f"{region}地区{cuisine}菜系TOP10餐厅", width=800, height=500)
        line.add("口味", shop_id_list, taste_list, xaxis_rotate=15, yaxis_min=8.8)
        line.add("环境", shop_id_list, env_list, xaxis_rotate=15, yaxis_min=8.8)
        line.add("服务", shop_id_list, serve_list, xaxis_rotate=15, yaxis_min=8.8)
        line.add("综合", shop_id_list, comp_list, xaxis_rotate=15, yaxis_min=8.8)

        line.render(path=os.path.join(save_path, f"{region}地区{cuisine}菜系口味，环境，服务及综合指数TOP10.html"))
        print(f"{region}地区{cuisine}菜系线形图绘制完成！\n")

    @staticmethod
    def draw_top5_bar(attr, count_list, price_list, save_path, region):
        """
        绘制 top5 柱状图
        绘制各地区餐厅分布最多的5大地区的餐厅数量及平均价格
        """
        print(f"开始绘制{region}餐厅5大热门地区餐厅数量及平均价格...")
        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        bar = Bar(f"{region}餐厅5大热门地区")
        bar.add("餐厅数量", attr, count_list)
        bar.add("平均价格", attr, price_list)
        bar.render(path=os.path.join(save_path, f"{region}餐厅5大热门地区餐厅数量及平均价格.html"))
        print(f"{region}餐厅5大热门地区餐厅数量及平均价格绘制完成！\n")

    @staticmethod
    def draw_user_bar(attr, vip_list, not_vip_list, save_path, region=None):
        """
        绘制不同地区VIP及非VIP数量的柱状图
        """
        info = "所有地区不同等级用户VIP数量的柱状图" if not region else f"{region}地区不同等级用户VIP数量的柱状图"
        print(f"开始绘制{info}...")

        save_path = os.path.join(base_save_path, save_path)
        if not os.path.exists(save_path):
            os.makedirs(save_path)

        bar = Bar(info)
        bar.add("VIP", attr, vip_list)
        bar.add("非VIP", attr, not_vip_list)
        bar.render(path=os.path.join(save_path, f"{info}.html"))

        print(f"{info}绘制完成！")
